package backends

import (
	"bytes"
	"compress/zlib"
	"github.com/flashmob/go-guerrilla/mail"
	"io"
	"sync"
)

// ----------------------------------------------------------------------------------
// Processor Name: compressor
// ----------------------------------------------------------------------------------
// Description   : Compress the e.Data (email data) and e.DeliveryHeader together
// ----------------------------------------------------------------------------------
// Config Options: None
// --------------:-------------------------------------------------------------------
// Input         : e.Data, e.DeliveryHeader generated by Header() processor
// ----------------------------------------------------------------------------------
// Output        : sets the pointer to a compressor in e.Info["zlib-compressor"]
//               : to write the compressed data, simply use fmt to print as a string,
//               : eg. fmt.Println("%s", e.Info["zlib-compressor"])
//               : or just call the String() func .Info["zlib-compressor"].String()
//               : Note that it can only be outputted once. It destroys the buffer
//               : after being printed
// ----------------------------------------------------------------------------------
func init() {
	processors["compressor"] = func() Decorator {
		return Compressor()
	}
}

// compressedData struct will be compressed using zlib when printed via fmt
type DataCompressor struct {
	ExtraHeaders []byte
	Data         *bytes.Buffer
	// the pool is used to recycle buffers to ease up on the garbage collector
	Pool *sync.Pool
}

// newCompressedData returns a new CompressedData
func newCompressor() *DataCompressor {
	// grab it from the pool
	var p = sync.Pool{
		// if not available, then create a new one
		New: func() interface{} {
			var b bytes.Buffer
			return &b
		},
	}
	return &DataCompressor{
		Pool: &p,
	}
}

// Set the extraheaders and buffer of data to compress
func (c *DataCompressor) set(b []byte, d *bytes.Buffer) {
	c.ExtraHeaders = b
	c.Data = d
}

// String implements the Stringer interface.
// Can only be called once!
// This is because the compression buffer will be reset and compressor will be returned to the pool
func (c *DataCompressor) String() string {
	if c.Data == nil {
		return ""
	}
	//borrow a buffer form the pool
	b := c.Pool.Get().(*bytes.Buffer)
	// put back in the pool
	defer func() {
		b.Reset()
		c.Pool.Put(b)
	}()

	var r *bytes.Reader
	w, _ := zlib.NewWriterLevel(b, zlib.BestSpeed)
	r = bytes.NewReader(c.ExtraHeaders)
	_, _ = io.Copy(w, r)
	_, _ = io.Copy(w, c.Data)
	_ = w.Close()
	return b.String()
}

// clear it, without clearing the pool
func (c *DataCompressor) clear() {
	c.ExtraHeaders = []byte{}
	c.Data = nil
}

func Compressor() Decorator {
	return func(p Processor) Processor {
		return ProcessWith(func(e *mail.Envelope, task SelectTask) (Result, error) {
			if task == TaskSaveMail {
				compressor := newCompressor()
				compressor.set([]byte(e.DeliveryHeader), &e.Data)
				// put the pointer in there for other processors to use later in the line
				e.Values["zlib-compressor"] = compressor
				// continue to the next Processor in the decorator stack
				return p.Process(e, task)
			} else {
				return p.Process(e, task)
			}
		})
	}
}
